﻿Imports System.Text
Imports System.Windows.Input
Imports Microsoft.VisualStudio.Language.Intellisense
Imports Microsoft.VisualStudio.RegularExpression.Intellisense.CompletionProviders
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports System.ComponentModel
Imports System.Windows

Namespace Microsoft.VisualStudio.RegularExpression.Intellisense
	''' <summary>
	''' Triggers the intellisense completion for the regex editor.
	''' </summary>
	Friend Class RegexCompletionController
		Implements IIntellisenseController
		Private subjectTextView As ITextView
		Private subjectBuffers As IList(Of ITextBuffer)
        Private completionBroker As ICompletionBroker
		Private activeSession As ICompletionSession
		Private keyTypeConverter As KeyTypeConverter
		Private selectedCompletionBeforeCommit As RegexCompletion

		''' <summary>
		''' The predicate specifies when the completion provider should be used.
		''' </summary>
		Private completionProviders As Dictionary(Of Predicate(Of KeyEventArgs), RegexCompletionProvider)

        Friend Sub New(ByVal subjectBuffers As IList(Of ITextBuffer), ByVal subjectTextView As ITextView, ByVal completionBrokerMap As ICompletionBroker)
            Me.subjectBuffers = subjectBuffers
            Me.subjectTextView = subjectTextView
            Me.completionBroker = completionBrokerMap
            Me.keyTypeConverter = New KeyTypeConverter

            Me.BuildCompletionProviders()
            Me.AttachToKeyEvents()
        End Sub

		Private Sub BuildCompletionProviders()
			Me.completionProviders = New Dictionary(Of Predicate(Of KeyEventArgs), RegexCompletionProvider)()
            Me.completionProviders.Add(Function(e) keyTypeConverter.ConvertToChar(e.Key).Equals("{"c), New BracketCompletions)
            Me.completionProviders.Add(Function(e) keyTypeConverter.ConvertToChar(e.Key).Equals("["c), New SquareBracketCompletions)
            Me.completionProviders.Add(Function(e) keyTypeConverter.ConvertToChar(e.Key).Equals("("c), New ParenthesisCompletions)
            Me.completionProviders.Add(Function(e) keyTypeConverter.ConvertToChar(e.Key).Equals("\"c), New SlashCompletions)
		End Sub

        Public Sub ConnectSubjectBuffer(ByVal subjectBuffer As ITextBuffer) Implements IIntellisenseController.ConnectSubjectBuffer
        End Sub

        Public Sub DisconnectSubjectBuffer(ByVal subjectBuffer As ITextBuffer) Implements IIntellisenseController.DisconnectSubjectBuffer
        End Sub

		''' <summary>
		''' Detaches the events
		''' </summary>
		''' <param name="textView"></param>
        Public Sub Detach(ByVal textView As ITextView) Implements IIntellisenseController.Detach
            If textView Is Nothing Then
                Throw New InvalidOperationException("Already detached from text view")
            End If
            If Me.subjectTextView IsNot textView Then
                Throw New ArgumentException("Not attached to specified text view", "textView")
            End If

            RemoveHandler RegexKeyProcessor.KeyDownEvent, AddressOf OnKeyDown
            RemoveHandler RegexKeyProcessor.KeyUpEvent, AddressOf OnKeyUp
        End Sub

		''' <summary>
		''' Handles the key up event.
		''' The intellisense window is dismissed when one presses ESC key
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		Private Sub OnKeyUp(ByVal sender As Object, ByVal e As KeyEventArgs)
			If activeSession IsNot Nothing Then
				If e.Key = Key.Escape Then
					activeSession.Dismiss()
					e.Handled = True
				End If

				If e.Key = Key.Enter Then
					If Me.activeSession.SelectedCompletionSet.SelectionStatus IsNot Nothing AndAlso Me.activeSession.SelectedCompletionSet.SelectionStatus.IsSelected Then
						selectedCompletionBeforeCommit = TryCast(Me.activeSession.SelectedCompletionSet.SelectionStatus.Completion, RegexCompletion)
						activeSession.Commit()
					Else
						activeSession.Dismiss()
					End If
					e.Handled = True
				End If
			End If
		End Sub


		''' <summary>
		''' Returns the first completion provider that matches the event args.
		''' </summary>
		''' <param name="e"></param>
		''' <returns></returns>
		Private Function GetCompletionProvider(ByVal e As KeyEventArgs) As RegexCompletionProvider
			For Each pair As KeyValuePair(Of Predicate(Of KeyEventArgs), RegexCompletionProvider) In Me.completionProviders
				If pair.Key(e) Then
					Return pair.Value
				End If
			Next pair

			Return Nothing
		End Function

		''' <summary>
		''' Triggers Statement completion when appropriate keys are pressed ('[', '{', '\', '(')
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		Private Sub OnKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
			' Make sure that this event happened on the same text view to which we're attached.
            Dim textView = TryCast(sender, ITextView)
			If Me.subjectTextView IsNot textView Then
				Return
			End If

			' Find the completion provider associated with the pressed key
            Dim completionProvider = GetCompletionProvider(e)

			If completionProvider IsNot Nothing Then
				If activeSession IsNot Nothing Then
					activeSession.Dismiss()
				End If

				' determine which subject buffer is affected by looking at the caret position
                Dim caretPoint = textView.Caret.Position.Point.GetPoint(Function(textBuffer) subjectBuffers.Contains(textBuffer), PositionAffinity.Predecessor)

                If caretPoint.HasValue Then
                    ' the invocation occurred in a subject buffer of interest to us
                    Dim broker = completionBroker
                    Dim triggerPoint = caretPoint.Value.Snapshot.CreateTrackingPoint(caretPoint.Value.Position, PointTrackingMode.Positive)

                    ' Create a completion session
                    activeSession = broker.CreateCompletionSession(textView, triggerPoint, True)
                    ' Set the completion provider that will be used by the completion source
                    activeSession.Properties.AddProperty(RegexCompletionProvider.CompletionProviderSessionKey, completionProvider)
                    ' Attach to the session events
                    AddHandler activeSession.Dismissed, AddressOf OnActiveSessionDismissed
                    AddHandler activeSession.Committed, AddressOf OnActiveSessionCommitted

                    Me.subjectTextView.Properties.GetProperty(Of TextViewEventManager)(TextViewEventManager.Key).OnIntellisenseSessionStart(New EventArgs)
                    ' Start the completion session. The intellisense will be triggered.
                    activeSession.Start()
                End If
			ElseIf e.Key = Key.Down AndAlso activeSession IsNot Nothing Then
				SelectNextCompletion()
                e.Handled = True            
			ElseIf e.Key = Key.Up AndAlso activeSession IsNot Nothing Then
				SelectPreviousCompletion()
				e.Handled = True
			End If
		End Sub

		Private Function GetRegexCompletionSet() As CompletionSet
            Return activeSession.CompletionSets.FirstOrDefault(Function(mySet) mySet.DisplayName = RegexCompletionSource.CompletionSetName)
		End Function

		Private Sub SelectNextCompletion()
            Dim selectedCompletionIndex = GetRegexCompletionSet().Completions.IndexOf(activeSession.SelectedCompletionSet.SelectionStatus.Completion)
            If ++selectedCompletionIndex < GetRegexCompletionSet().Completions.Count Then
                activeSession.SelectedCompletionSet.SelectionStatus = New CompletionSelectionStatus(Me.GetRegexCompletionSet().Completions(selectedCompletionIndex), True, True)
            End If
		End Sub

		Private Sub SelectPreviousCompletion()
            Dim selectedCompletionIndex = GetRegexCompletionSet().Completions.IndexOf(activeSession.SelectedCompletionSet.SelectionStatus.Completion)
            If --selectedCompletionIndex >= 0 Then
                activeSession.SelectedCompletionSet.SelectionStatus = New CompletionSelectionStatus(Me.GetRegexCompletionSet().Completions(selectedCompletionIndex), True, True)
            End If
		End Sub

		Private Sub OnActiveSessionDismissed(ByVal sender As Object, ByVal e As System.EventArgs)
            Me.subjectTextView.Properties.GetProperty(Of TextViewEventManager)(TextViewEventManager.Key).OnIntellisenseSessionEnd(New EventArgs)
			activeSession = Nothing
		End Sub

		Private Sub OnActiveSessionCommitted(ByVal sender As Object, ByVal e As System.EventArgs)
			If selectedCompletionBeforeCommit IsNot Nothing Then
				activeSession.TextView.Caret.MoveTo(New VirtualSnapshotPoint(activeSession.TextView.Caret.Position.BufferPosition.Subtract(selectedCompletionBeforeCommit.CommitPositionModifier)))
			End If
		End Sub

		''' <summary>
		''' Attaches to the key events.
		''' </summary>
		Private Sub AttachToKeyEvents()
			AddHandler RegexKeyProcessor.KeyDownEvent, AddressOf OnKeyDown
			AddHandler RegexKeyProcessor.KeyUpEvent, AddressOf OnKeyUp
		End Sub

	End Class
End Namespace
